﻿using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

using ProgramMain.Framework;
using ProgramMain.Google;

namespace ProgramMain.Layers
{
    public class GraphicLayer : WorkerMessageThread
    {
        private Bitmap[] offScreen = {null, null};
        private Graphics[] offScreenDC = {null, null};

        private readonly SemaphoreSlim lockDC = new SemaphoreSlim(1, 1);
        private enum ActiveDrawBuffer {DC0 = 0, DC1 = 1};
        private ActiveDrawBuffer ActiveDC = ActiveDrawBuffer.DC0;

        private Coordinate _CenterCoordinate = new Coordinate(
            Properties.Settings.Default.StartLongitude, 
            Properties.Settings.Default.StartLatitude);
        private int _Level = Properties.Settings.Default.StartZoomLevel;

        public Coordinate CenterCoordinate
        {
            get
            {
                return (Coordinate)_CenterCoordinate.Clone();
            }
            set
            {
                SetCenterCoordinate(value, _Level);
            }
        }
        public int Level
        {
            get
            {
                return _Level;
            }
            set
            {
                SetCenterCoordinate(_CenterCoordinate, value);
            }
        }

        private GoogleRectangle _GoogleScreenView;
        public GoogleRectangle GoogleScreenView
        {
            get
            {
                return (GoogleRectangle)_GoogleScreenView.Clone();
            }
        }
        private CoordinateRectangle _CoordinateScreenView;
        protected CoordinateRectangle CoordinateScreenView
        {
            get
            {
                return (CoordinateRectangle)_CoordinateScreenView.Clone();
            }
        }

        public int Width { get; private set; }

        public int Height { get; private set; }

        protected PixelFormat PIFormat { get; private set; }

        public GraphicLayer(int pWidth, int pHeight, Coordinate pCenterCoordinate, int pLevel, 
            Control pDelegateCOntrol, PixelFormat pPIFormat) :
            base(pDelegateCOntrol)
        {
            Width = pWidth;
            Height = pHeight;

            _CenterCoordinate = pCenterCoordinate;  // Географическая широта, Географическая долгота, Масштаб (Zoom) GoogleMap-а
            _Level = pLevel;

            PIFormat = pPIFormat;

            Resize(false);
            //событие на отрисовку слоя
            DrawLayerEvent += new OwnerEventDelegate<InvalidateLayerEventArgs>(OnIvalidateLayer);
        }

        /// <summary>
        /// Параметры события, срабатывает на изменение отрисовки слоя
        /// </summary>
        public class InvalidateLayerEventArgs : OwnerEventArgs
        {
            public InvalidateLayerEventArgs()
            {

            }

            public InvalidateLayerEventArgs(Rectangle pRect)
            {
                ClipRectangle = pRect;
            }

            public readonly Rectangle ClipRectangle;
        }

        public event OwnerEventHandler<InvalidateLayerEventArgs> DrawLayerBuffer;

        private OwnerEventDelegate<InvalidateLayerEventArgs> DrawLayerEvent;

        private void OnIvalidateLayer(InvalidateLayerEventArgs EventArgs)
        {
            if (DrawLayerBuffer != null)
            {
                DrawLayerBuffer(this, EventArgs);
            }
        }

        protected void FireIvalidateLayer(Rectangle ClipRectangle)
        {
            FireOwnerEvent(DrawLayerEvent, new InvalidateLayerEventArgs(ClipRectangle));
        }

        protected override bool DispatchThreadEvents(WorkerEvent WorkerEvent)
        {
            switch (WorkerEvent.EventType)
            {
                case WorkerEventType.RedrawLayer:
                    {
                        DrawLayer(new Rectangle(0, 0, Width, Height));
                        return true;
                    }
            }
            return base.DispatchThreadEvents(WorkerEvent);
        }

        protected virtual bool SetCenterCoordinate(Coordinate center, int level)
        {
            if (_Level != level
                || new GoogleCoordinate(_CenterCoordinate, level).CompareTo(
                new GoogleCoordinate(center, level)) != 0)
            {
                _CenterCoordinate = center;
                _Level = level;
                TranslateCoords();
                Update(new Rectangle(0, 0, Width, Height));
                return true;
            }
            return false;
        }

        protected Bitmap CreateCompatibleBitmap(Bitmap org, PixelFormat PIFormat)
        {
            Bitmap GDIbmp = new Bitmap(org.Width, org.Height, PIFormat);
            Graphics GDIContext = Graphics.FromImage(GDIbmp);
            GDIContext.DrawImage(org, 0, 0, org.Width, org.Height);

            return GDIbmp;
        }

        protected Bitmap CreateCompatibleBitmap(Bitmap org, int Width, int Height, PixelFormat PIFormat)
        {
            Bitmap GDIbmp = new Bitmap(Width, Height, PIFormat);
            Graphics GDIContext = Graphics.FromImage(GDIbmp);
            if (org != null)
            {
                GDIContext.DrawImageUnscaledAndClipped(org, new Rectangle(0, 0, Width, Height));
            }
            else
            {
                GDIContext.Clear(Color.White);
            }
            return GDIbmp;
        }

        private void Resize(bool bUpdate)
        {
            try
            {
                lockDC.Wait();
                
                if (Width > 0 && Height > 0 && !Terminating)
                {
                    for (int i = 0; i <= 1; i++)
                    {
                        offScreen[i] = new Bitmap(Width, Height, PIFormat);
                        offScreenDC[i] = Graphics.FromImage(offScreen[i]);

                        offScreenDC[i].InterpolationMode = InterpolationMode.Low;
                        offScreenDC[i].CompositingQuality = CompositingQuality.HighSpeed;
                        offScreenDC[i].SmoothingMode = SmoothingMode.HighSpeed;
                    }
                    ActiveDC = ActiveDrawBuffer.DC0;
                    
                    TranslateCoords();

                    if (bUpdate)
                        Update(new Rectangle(0, 0, Width, Height));
                }
                else
                {
                    offScreen[0] = null;
                    offScreenDC[0] = null;
                    offScreen[1] = null;
                    offScreenDC[1] = null;
                }
            }
            finally
            {
                lockDC.Release();
            }
        }

        public void Resize(int pWidth, int pHeight)
        {
            if (pWidth != Width || pHeight != Height)
            {
                Width = pWidth;
                Height = pHeight;
                Resize(true);
            }
        }

        virtual protected void TranslateCoords()
        {
            //определяем видимую область в целочисленных координатах гугла
            //относительно заданной центральной точки
            _GoogleScreenView = _CenterCoordinate.GoogleScreenViewFromCenter(Width, Height, _Level);
            //определяем видимую область в широте и долготе
            _CoordinateScreenView = (CoordinateRectangle)GoogleScreenView;
        }

        public void Update()
        {
            Update(Rectangle.Empty);
        }

        public void Update(Rectangle ClipRectangle)
        {
            if (offScreenDC != null && !Terminating)
            {
                PutWorkerThreadEvent(WorkerEventType.RedrawLayer, true, EventPriorityType.BelowNormal);
            }
        }

        virtual protected void DrawLayer(Rectangle ClipRectangle)
        {
        }

        protected void DrawBitmap(Bitmap bmp, int X, int Y)
        {
            try
            {
                lockDC.Wait();
                offScreenDC[0].DrawImageUnscaled(bmp, X, Y);
            }
            finally
            {
                lockDC.Release();
            }
        }

        protected void DrawBitmap(Bitmap bmp, Point point)
        {
            DrawBitmap(bmp, point.X, point.Y);
        }

        protected void DrawString(string Caption, int Size, int X, int Y)
        {
            Font font = new Font("Arial", Size, FontStyle.Regular);
            SolidBrush brush = new SolidBrush(Properties.Settings.Default.MapCaptionColor);
            try
            {
                lockDC.Wait();
                offScreenDC[0].TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
                offScreenDC[0].DrawString(Caption, font, brush, X, Y);
            }
            finally
            {
                lockDC.Release();
            }
        }

        protected void DrawString(string Caption, int Size, Point pt)
        {
            DrawString(Caption, Size, pt.X, pt.Y);
        }

        public void DrawLine(Rectangle lineRectangle, int Width)
        {
            Pen pen = new Pen(Color.Blue, Width);
            try
            {
                lockDC.Wait();
                offScreenDC[0].DrawLine(pen, lineRectangle.Left, lineRectangle.Top, lineRectangle.Right, lineRectangle.Bottom);
            }
            finally
            {
                lockDC.Release();
            }
        }

        public void DrawLine(Rectangle lineRectangle, int Width, Color color)
        {
            Pen pen = new Pen(color, Width);
            try
            {
                lockDC.Wait();
                offScreenDC[0].DrawLine(pen, lineRectangle.Left, lineRectangle.Top, lineRectangle.Right, lineRectangle.Bottom);
            }
            finally
            {
                lockDC.Release();
            }
        }
        protected void FillColor(Color color)
        {
            try
            {
                lockDC.Wait();
                offScreenDC[0].Clear(color);
            }
            finally
            {
                lockDC.Release();
            }
        }

        protected void FillTransparent()
        {
            FillColor(Color.FromArgb(0, 255, 255, 255));
        }

        public void DrawBuffer(Graphics clientDC, Rectangle ClipRectangle)
        {
            try
            {
                lockDC.Wait();
                if (this.Width == ClipRectangle.Width && this.Height == ClipRectangle.Height)
                    clientDC.DrawImageUnscaled(offScreen[(int)ActiveDC], 0, 0);
                else
                    clientDC.DrawImage(
                        offScreen[(int)ActiveDC], 
                        ClipRectangle.X, ClipRectangle.Y, 
                        ClipRectangle, GraphicsUnit.Pixel);
            }
            finally
            {
                lockDC.Release();
            }
        }
        
        protected void SwapDrawBuffer()
        {
            try
            {
                lockDC.Wait();
                
                if (ActiveDC == ActiveDrawBuffer.DC0)
                {
                    Bitmap tmpScreen = offScreen[0];
                    Graphics tmpScreenDC = offScreenDC[0];
                    offScreen[0] = offScreen[1];
                    offScreenDC[0] = offScreenDC[1];
                    offScreen[1] = tmpScreen;
                    offScreenDC[1] = tmpScreenDC;

                    ActiveDC = ActiveDrawBuffer.DC1;
                }
                else
                {
                    ActiveDC = ActiveDrawBuffer.DC0;
                }
            }
            finally
            {
                lockDC.Release();
            }
        }
    }
}
